/**
 * author:csdn猫哥
 * qq:534117529
 * blog:https://blog.csdn.net/yyz_1987
 */
//axiosHttp.ets
import fs from '@ohos.file.fs';
import axios, {
  AxiosError,
  AxiosInstance,
  AxiosHeaders,
  AxiosRequestHeaders,
  AxiosResponse,
  FormData,
  AxiosProgressEvent,
  InternalAxiosRequestConfig
} from "@ohos/axios";

interface HttpResponse<T>{
  data: T;
  status: number;
  statusText: string;
  config: HttpRequestConfig;
}
export type HttpPromise<T> = Promise<HttpResponse<T>>;

// 鸿蒙ArkTS文件上传相关接口定义
/**
上传类型支持uri和ArrayBuffer，
uri支持“internal”协议类型和沙箱路径。
"internal://cache/"为必填字段，示例： internal://cache/path/to/file.txt；
沙箱路径示例：cacheDir + '/hello.txt'
 */
export interface UploadFile {
  buffer?: ArrayBuffer;
  fileName?: string;
  mimeType?: string;
  uri?:string;
}

export interface FileUploadConfig extends HttpRequestConfig {
  file?: UploadFile | UploadFile[];
  fileFieldName?: string; // 文件字段名，默认为 'file'
  additionalData?: Record<string, any>; // 额外的表单数据
  onUploadProgress?: (progressEvent: any) => void; // 上传进度回调
}

export interface FileInfo {
  name: string;
  size: number;
  type: string;
}

// 文件下载相关接口定义
export interface FileDownloadConfig extends HttpRequestConfig {
  filePath: string; // 本地文件保存路径
  onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void; // 下载进度回调
  overwrite?: boolean; // 是否覆盖已存在的文件，默认为true
}
/**
 * 封装后，不支持传入拦截器
 * 需要自己定义接口继承 AxiosRequestConfig类型
 * 从而支持传入拦截器，但拦截器选项应为可选属性
 * 之后请求实例传入的options为继承了AxiosRequestConfig的自定义类型
 */
interface InterceptorHooks {
  requestInterceptor?: (config: HttpRequestConfig) => Promise<HttpRequestConfig>;
  requestInterceptorCatch?: (error: any) => any;
  responseInterceptor?: (response: AxiosResponse) => AxiosResponse | Promise<AxiosResponse>;
  responseInterceptorCatch?: (error: any) => any;
}

// @ts-ignore
interface HttpRequestConfig extends InternalAxiosRequestConfig {
  showLoading?: boolean; //是否展示请求loading
  checkResultCode?: boolean; //是否检验响应结果码
  checkLoginState?: boolean; //校验用户登陆状态
  needJumpToLogin?: boolean; //是否需要跳转到登陆页面
  interceptorHooks?: InterceptorHooks;//拦截器
  headers?: AxiosRequestHeaders;
  errorHandler?: (error: any) => void; //错误处理
}


/**
 * 网络请求构造
 * 基于axios框架实现
 */
export class AxiosHttpRequest {
  config: HttpRequestConfig;
  interceptorHooks?: InterceptorHooks;
  instance: AxiosInstance;

  constructor(options: HttpRequestConfig) {
    this.config = options;
    this.interceptorHooks = options.interceptorHooks;
    this.instance = axios.create(options);
    this.setupInterceptor()
  }

  setupInterceptor(): void {
    this.instance.interceptors.request.use(
      // 这里主要是高版本的axios中设置拦截器的时候里面的Config属性必须是InternalAxiosRequestConfig，
      // 但是InternalAxiosRequestConfig里面的headers是必传，所以在实现的子类我设置成非必传会报错，加了个忽略注解
      // @ts-ignore
      this.interceptorHooks?.requestInterceptor,
      this.interceptorHooks?.requestInterceptorCatch,
    );
    this.instance.interceptors.response.use(
      this.interceptorHooks?.responseInterceptor,
      this.interceptorHooks?.responseInterceptorCatch,
    );
  }

  // 类型参数的作用，T决定AxiosResponse实例中data的类型
  request<T = any>(config: HttpRequestConfig): HttpPromise<T> {
    return new Promise<HttpResponse<T>>((resolve, reject) => {
      this.instance
        .request<any, HttpResponse<T>>(config)
        .then(res => {
          resolve(res);
        })
        .catch((err) => {
          // 使用传入的 errorHandler 处理错误
          const errorHandler = config.errorHandler || errorHandlerDefault;
          errorHandler(err); 
          if (err) {
            reject(err);
          }
        });
    });
  }

  get<T = any>(config: HttpRequestConfig): HttpPromise<T> {
    return this.request({ ...config, method: 'GET' });
  }

  post<T = any>(config: HttpRequestConfig): HttpPromise<T> {
    return this.request({ ...config, method: 'POST' });
  }

  delete<T = any>(config: HttpRequestConfig): HttpPromise<T> {
    return this.request({ ...config, method: 'DELETE' });
  }

  patch<T = any>(config: HttpRequestConfig): HttpPromise<T> {
    return this.request({ ...config, method: 'PATCH' });
  }

  put<T = any>(config: HttpRequestConfig): HttpPromise<T> {
    return this.request({ ...config, method: 'PUT' });
  }

  /**
   * 上传单个文件或多个文件
   * @param config 文件上传配置
   * @returns Promise<HttpResponse<T>>
   */
  uploadFile<T = any>(config: FileUploadConfig): HttpPromise<T> {
    return new Promise<HttpResponse<T>>((resolve, reject) => {
      if (!config.file) {
        reject(new Error('文件不能为空'));
        return;
      }

      const formData = new FormData();
      const fileFieldName = config.fileFieldName || 'file';

      // 处理单个或多个文件
      const files = Array.isArray(config.file) ? config.file : [config.file];
      files.forEach((file, index) => {
        const fieldName = Array.isArray(config.file) ? `${fileFieldName}[${index}]` : fileFieldName;

        // 鸿蒙ArkTS FormData.append 支持第三个参数设置文件名和类型
        if (file.mimeType) {
          formData.append(fieldName, file.buffer, {
            filename: file.fileName,
            type: file.mimeType
          });
        } else if (file.buffer){
          formData.append(fieldName, file.buffer, {
            filename: file.fileName
          });
        }else if (file.uri){
          formData.append(fieldName, file.uri);
        }
      });

      // 添加额外的表单数据
      if (config.additionalData) {
        Object.keys(config.additionalData).forEach(key => {
          formData.append(key, config.additionalData![key]);
        });
      }

      const uploadConfig: HttpRequestConfig = {
        ...config,
        method: 'POST',
        data: formData,
        headers: new AxiosHeaders({
          ...config.headers,
          'Content-Type': 'multipart/form-data'
        })
      };

      // 添加上传进度监听
      if (config.onUploadProgress) {
        uploadConfig.onUploadProgress = config.onUploadProgress;
      }

      this.request<T>(uploadConfig)
        .then(resolve)
        .catch(reject);
    });
  }

  /**
   * 上传多个文件
   * @param config 文件上传配置
   * @returns Promise<HttpResponse<T>>
   */
  uploadFiles<T = any>(config: FileUploadConfig): HttpPromise<T> {
    if (!Array.isArray(config.file)) {
      return Promise.reject(new Error('uploadFiles方法需要传入文件数组'));
    }
    return this.uploadFile<T>(config);
  }

  /**
   * 获取文件信息
   * @param file 文件对象
   * @returns FileInfo
   */
  getFileInfo(file: UploadFile): FileInfo {
    return {
      name: file.fileName,
      size: file.buffer.byteLength,
      type: file.mimeType || 'application/octet-stream'
    };
  }

  /**
   * 验证文件类型
   * @param file 文件对象
   * @param allowedTypes 允许的文件类型数组
   * @returns boolean
   */
  validateFileType(file: UploadFile, allowedTypes: string[]): boolean {
    const fileType = file.mimeType || 'application/octet-stream';
    return allowedTypes.includes(fileType);
  }

  /**
   * 验证文件大小
   * @param file 文件对象
   * @param maxSize 最大文件大小（字节）
   * @returns boolean
   */
  validateFileSize(file: UploadFile, maxSize: number): boolean {
    return file.buffer.byteLength <= maxSize;
  }

  /**
   * 创建文件上传配置
   * @param url 上传地址
   * @param file 文件对象
   * @param options 其他配置选项
   * @returns FileUploadConfig
   */
  createUploadConfig(
    url: string,
    file: UploadFile | UploadFile[],
    options: Partial<FileUploadConfig> = {}
  ): FileUploadConfig {
    return {
      url,
      file,
      fileFieldName: 'file',
      ...options
    };
  }

  /**
   * 下载文件
   * @param config 文件下载配置
   * @returns Promise<HttpResponse<T>>
   */
  downloadFile<T = any>(config: FileDownloadConfig): HttpPromise<T> {
    return new Promise<HttpResponse<T>>((resolve, reject) => {
      if (!config.filePath) {
        reject(new Error('文件保存路径不能为空'));
        return;
      }

      const { filePath, overwrite = true, onDownloadProgress } = config;

      // 如果文件已存在且允许覆盖，则先删除文件
      if (overwrite) {
        try {
          fs.accessSync(filePath);
          fs.unlinkSync(filePath);
        } catch(err) {
          // 文件不存在，忽略错误
        }
      }

      const downloadConfig: HttpRequestConfig = {
        ...config,
        method: 'GET',
        filePath: filePath
      };

      // 添加下载进度监听
      if (onDownloadProgress) {
        downloadConfig.onDownloadProgress = onDownloadProgress;
      }

      this.request<T>(downloadConfig)
        .then(resolve)
        .catch(reject);
    });
  }

  /**
   * 创建文件下载配置
   * @param url 下载地址
   * @param filePath 本地文件保存路径
   * @param options 其他配置选项
   * @returns FileDownloadConfig
   */
  createDownloadConfig(
    url: string,
    filePath: string,
    options: Partial<FileDownloadConfig> = {}
  ): FileDownloadConfig {
    return {
      url,
      filePath,
      overwrite: true,
      ...options
    };
  }

  /**
   * 检查文件是否存在
   * @param filePath 文件路径
   * @returns boolean
   */
  fileExists(filePath: string): boolean {
    try {
      fs.accessSync(filePath);
      return true;
    } catch(err) {
      return false;
    }
  }

  /**
   * 删除文件
   * @param filePath 文件路径
   * @returns boolean
   */
  deleteFile(filePath: string): boolean {
    try {
      if (this.fileExists(filePath)) {
        fs.unlinkSync(filePath);
        return true;
      }
      return false;
    } catch(err) {
      return false;
    }
  }

  /**
   * 获取文件大小
   * @param filePath 文件路径
   * @returns number 文件大小（字节），文件不存在返回-1
   */
  getFileSize(filePath: string): number {
    try {
      if (this.fileExists(filePath)) {
        const stat = fs.statSync(filePath);
        return stat.size;
      }
      return -1;
    } catch(err) {
      return -1;
    }
  }
}

function errorHandlerDefault(error: any) {
  if (error instanceof AxiosError) {
    //showToast(error.message)
  } else if (error != undefined && error.response != undefined && error.response.status) {
    switch (error.response.status) {
    // 401: 未登录
    // 未登录则跳转登录页面，并携带当前页面的路径
    // 在登录成功后返回当前页面，这一步需要在登录页操作。
      case 401:

        break;
    // 403 token过期
    // 登录过期对用户进行提示
    // 清除本地token和清空vuex中token对象
    // 跳转登录页面
      case 403:
        //showToast("登录过期，请重新登录")
      // 清除token
      // localStorage.removeItem('token');
        break;
    // 404请求不存在
      case 404:
        //showToast("网络请求不存在")
        break;

    // 其他错误，直接抛出错误提示
      default:
        //showToast(error.response.data.message)
    }

  }
}
export{AxiosRequestHeaders,AxiosError,AxiosHeaders,AxiosProgressEvent,FormData,AxiosResponse};
export default AxiosHttpRequest;